﻿/* UTF-8 */

/*
 * glyphtexture.c: make glyph texture
 * Copyright 2016 tianxianglong. All rights reserved.
 */

/*
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:

 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.

 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#ifndef UNICODE
#define UNICODE
#endif

#ifndef _UNICODE
#define _UNICODE
#endif

#ifndef _CRT_SECURE_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS
#endif

#include <Windows.h>
#include <windowsx.h>
#include "glyphtexture.h"
#include "raster.h"
#include "render_window.h"

void InitCoordinate(GLYPHTEXTUREPTR Texture)
{
	Texture->pen_x = 0;
	Texture->pen_y = 0;
	Texture->row_h = 0;
}

void AddChar(GLYPHTEXTUREPTR Texture, int idx, WCHAR ch, int x, int y, int cx, int cy)
{
	Texture->codepoints[idx] = ch;
	Texture->Coords[idx].x = Texture->pen_x;
	Texture->Coords[idx].y = Texture->pen_y;
	Texture->Coords[idx].x2 = Texture->pen_x + cx;
	Texture->Coords[idx].y2 = Texture->pen_y + cy;
}

/*
 * Texture: the texture to be filled
 * Font: the font to used
 * Str: non-null terminated string
 * return: the remaining string
 */
static LPCWSTR fill_texture(GLYPHTEXTUREPTR Texture, FONTOBJECTPTR Font, LPCWSTR Str)
{
	while (Str != NULL 
		&& Str < Font->Count + &Font->codepoints[0]
		&& Texture->cch < 4096)
	{
		WCHAR ch = *Str++;
		USHORT index = Font->indices[ch];
		UINT cx = Font->metrics[index].gmBlackBoxX;
		UINT cy = Font->metrics[index].gmBlackBoxY;

		/* test if has enough space in horizontal. if not, go down to next row */
		if (Texture->pen_x + cx > Texture->Width)
		{
			Texture->pen_x = 0;
			Texture->pen_y += Texture->row_h;
			Texture->row_h = 0;
		}

		/* if still has no enough space, return */
		if (Texture->pen_x + cx > Texture->Width)
		{
			return Str;
		}

		/* test if has enough space in vertical to contain a new character */
		if (Texture->Height != 0 && Texture->Height < Texture->pen_y + cy)
		{
			return Str;
		}

		AddChar(Texture, Texture->cch, ch, Texture->pen_x, Texture->pen_y, cx, cy);

		++Texture->cch;

		Texture->row_h = max(Texture->row_h, cy);
		Texture->pen_x += cx;
	}

	return NULL;
}

void
SetBitmapPixelData(
	PIXELDATAPTR PixelData,
	FONTOBJECTPTR Font,
	LPBYTE Bits,
	UINT x,
	UINT y,
	UINT cx,
	UINT cy
)
{
	int bpp = (Font->Format == GGO_BITMAP ? 1 : 8);
	int src_pitch = (bpp * cx + 31) / 32 * 4;
	UINT src_cx = cx;
	BYTE *dst = PixelData->buffer;
	BYTE *DestBits = dst + y * PixelData->pitch;
	BYTE *SrcBits = Bits;

	for (UINT row = 0; row < cy; ++row)
	{
		for (UINT bitpos = 0; bitpos < src_cx; ++bitpos)
		{
			BYTE src_px = 0;
			if (bpp == 1)
			{
				src_px = UNPACK_PIXEL(SrcBits[bitpos >> 3], bitpos & 0x00000007);
				src_px = ~src_px + 0x01;
			}
			else
			{
				src_px = SrcBits[bitpos];
				src_px = (BYTE)((float)src_px / 64.0f * 255.0f);
			}

			int dst_bitpos = bitpos + x;
			DestBits[dst_bitpos] = src_px;
		}

		DestBits += PixelData->pitch;
		SrcBits += src_pitch;
	}
}

void
SetTexturePixelData(
	PIXELDATAPTR PixelData,
	GLYPHTEXTUREPTR GlyphTexture,
	FONTOBJECTPTR Font
)
{
	for (size_t idx = 0; idx < GlyphTexture->cch; ++idx)
	{
		WCHAR ch = GlyphTexture->codepoints[idx];
		USHORT index = Font->indices[ch];
		UINT cx = Font->metrics[index].gmBlackBoxX;
		UINT cy = Font->metrics[index].gmBlackBoxY;

		UINT x = GlyphTexture->Coords[idx].x;
		UINT y = GlyphTexture->Coords[idx].y;

		if (x + cx <= PixelData->cx && y + cy <= PixelData->cy)
		{
			if (Font->bitmaps[index] != NULL)
			{
				SetBitmapPixelData(PixelData, Font, Font->bitmaps[index], x, y, cx, cy);
			}
		}
	}
}

GLYPHTEXTURESETPTR MakeGlyphTexture(FONTOBJECTPTR Font)
{
	GLYPHTEXTURESETPTR GlyphTexSet;

	GlyphTexSet = malloc(sizeof(*GlyphTexSet));

	if (NULL == GlyphTexSet)
		return NULL;

	memset(GlyphTexSet, 0, sizeof(*GlyphTexSet));

	GlyphTexSet->Font = Font;

	const UINT Width = 512;
	const UINT Height = 512;
	
	GlyphTexSet->ctexture = 0;

	GetCodePointRanges(Font);

	GLYPHTEXTUREPTR CurrentTexture = NULL;
	const WCHAR *Ch = &Font->codepoints[0];
	while (Ch != NULL
		&& Ch < &Font->codepoints[65535]
		&& Ch < &Font->codepoints[0] + Font->Count
		)
	{
		if (CurrentTexture == NULL)
		{
			CurrentTexture = malloc(sizeof(*CurrentTexture));
			if (CurrentTexture == NULL)
				break;

			memset(&CurrentTexture->codepoints[0], 0, sizeof(CurrentTexture->codepoints));
			CurrentTexture->cch = 0;
			CurrentTexture->Width = Width;
			CurrentTexture->Height = Height;
			InitCoordinate(CurrentTexture);
		}
		
		
		Ch = fill_texture(CurrentTexture, Font, Ch);

		if (Ch != NULL)
		{
			GlyphTexSet->Textures[GlyphTexSet->ctexture++] = CurrentTexture;
			CurrentTexture = NULL;
		}
	}

	GlyphTexSet->Textures[GlyphTexSet->ctexture++] = CurrentTexture;
	CurrentTexture = NULL;

	for (UINT i = 0; i < GlyphTexSet->ctexture; ++i)
	{
		PixelDataPtr pd = MakeGrayBitmap(Height, Width);

		SetTexturePixelData(pd, GlyphTexSet->Textures[i], Font);

		CreateTexture(101 + i, Width, Height);

		const char *RowArray[512];
		BYTE *RowBuffer = pd->buffer;
		for (int i = 0; i < 512; ++i)
		{
			RowArray[i] = RowBuffer;
			RowBuffer += pd->pitch;
		}

		UpdateTexture(101 + i, RowArray);

		GlyphTexSet->Textures[i]->tid = 101 + i;

		//WCHAR FileName[512];
		//wsprintf(FileName, L"GlyphTexture%d.png", (int)(101 + i));
		//SaveTextureToFile(101 + i, FileName);
	}

	return GlyphTexSet;
}


GLYPHTEXTUREINDEXPTR MakeGlyphTextureIndex(GLYPHTEXTURESETPTR GlyphTextureSet)
{
	GLYPHTEXTUREINDEXPTR index = NULL;
	index = malloc(sizeof(*index));

	if (NULL == index)
		return NULL;

	memset(index, 0xFF, sizeof(*index));

	FONTOBJECTPTR Font = GlyphTextureSet->Font;

	for (UINT i = 0; i < GlyphTextureSet->ctexture; ++i)
	{
		for (USHORT j = 0; j < GlyphTextureSet->Textures[i]->cch; ++j)
		{
			GLYPHTEXTUREPTR Texture = GlyphTextureSet->Textures[i];
			WCHAR ch = Texture->codepoints[j];
			USHORT idx = Font->indices[ch];

			index->indices[ch] = (USHORT)j;
			index->texture[ch] = (USHORT)i;
			
			GLYPHCOORD coord = Texture->Coords[j];

			float cx = (float)Texture->Width;
			float cy = (float)Texture->Height;

			coord.u = (float)coord.x / cx;
			coord.u2 = (float)coord.x2 / cx;
			coord.v = (float)coord.y / cy;
			coord.v2 = (float)coord.y2 / cy;

			
			coord.ax = (SHORT)Font->metrics[idx].gmCellIncX;
			coord.ay = (SHORT)Font->metrics[idx].gmCellIncY;
			coord.ox = (SHORT)Font->metrics[idx].gmptGlyphOrigin.x;
			coord.oy = (SHORT)Font->metrics[idx].gmptGlyphOrigin.y;

			Texture->Coords[j] = coord;
		}
	}

	return index;
}

void FillContent(CODEPPOINTRANGEPTR Range)
{
	for (int i = 0; i < Range->cch; ++i)
	{
		*(Range->Content+i) = i + Range->chLow;
	}
	
	*(Range->Content+ Range->cch) = L'\0';
}

CODEPOINTSETPTR GetCodePointRanges(FONTOBJECTPTR Font)
{
	CODEPOINTSETPTR CodePointSet = NULL;

	CodePointSet = malloc(sizeof(*CodePointSet));

	for (DWORD i = 0; i < Font->glyphset->cRanges && i < 400; ++i)
	{
		CodePointSet->Ranges[i] = malloc(sizeof(*CodePointSet->Ranges[i]) * Font->glyphset->cRanges);
		CODEPPOINTRANGEPTR Ranges = CodePointSet->Ranges[i];
		Ranges->cch = Font->glyphset->ranges[i].cGlyphs;
		Ranges->chLow = Font->glyphset->ranges[i].wcLow;
		Ranges->Content = malloc(sizeof(*Ranges->Content) * (Ranges->cch + 1));
		FillContent(Ranges);
	}

	return CodePointSet;
}
